#include	"kheap.h"
#include	"paging.h"
#include	"monitor.h"
#include	"task.h"

/* kernel and current page directories */
struct page_directory *kernel_directory = (struct page_directory *)0;
struct page_directory *current_directory = (struct page_directory *)0;

/* frame bitmap: used or free */
u32int *frames;
u32int nframes;

/* start addr of kernel heap - defined in kheap.c */
extern u32int placement_addr;
extern struct heap *kheap;
extern struct heap *uheap;

/* using for page fault */
extern struct task *current_task;
extern struct task *sched_task;

/* debug flag */
extern u8int flag_task;

#define		INDEX_FROM_BIT(a)	((a) / (8 * 4))
#define		OFFSET_FROM_BIT(a)	((a) % (8 * 4))

static void set_frame(u32int frame_addr){
	
	u32int frame, idx, off;
	
	frame = frame_addr / 0x1000;
	idx = INDEX_FROM_BIT(frame);
	off = OFFSET_FROM_BIT(frame);

	frames[idx] |= (0x1 << off);
}

static void clear_frame(u32int frame_addr){

	u32int frame, idx, off;
	
	frame = frame_addr / 0x1000;
	idx = INDEX_FROM_BIT(frame);
	off = OFFSET_FROM_BIT(frame);

	frames[idx] &= ~(0x1 << off);
}

static int test_frame(u32int frame_addr){

	u32int frame, idx, off;

	frame = frame_addr / 0x1000;
	idx = INDEX_FROM_BIT(frame);
	off = OFFSET_FROM_BIT(frame);

	return (frames[idx] & (0x1 << off));
}

static u32int first_frame(){

	u32int i, j, to_test;

	for(i = 0; i < INDEX_FROM_BIT(nframes); i++){
		if(frames[i] != 0xFFFFFFFF){
			for(j = 0; j < 32; j++){
				to_test = 0x1 << j;
				if(!(frames[i] & to_test)){
					return i * 4 * 8 + j;
				}
			}
		}
	}
}

void alloc_frame(struct page *ppage, int is_kernel, int is_writeable){

	u32int idx;
	if(ppage -> frame){
		return;
	}else{
		idx = first_frame();
		if(idx == (u32int)(-1)){
			/* here we should PANIC for no free frame is available */
			monitor_write("PANIC: no free frame\n");
			return;
		}
		set_frame(idx * 0x1000);
		ppage -> present = 1;
		ppage -> rw = (is_writeable) ? 1 : 0;
		ppage -> user = (is_kernel) ? 0 : 1;
		ppage -> frame = idx;
	}
}


void free_frame(struct page *ppage){

	u32int frame;

	if(!(frame = ppage -> frame)){
		return;
	}else{
		clear_frame(frame);
		ppage -> frame = 0x0;
	}
}

void init_paging(){
	
	u32int mem_end_page = 0x1000000;	/* assume a 16MB physical memory */
	u32int phys;
	int i = 0;
	
	nframes = mem_end_page / 0x1000;
	frames = (u32int *)kmalloc(INDEX_FROM_BIT(nframes));
	memset(frames, 0, INDEX_FROM_BIT(nframes));

	kernel_directory = (struct page_directory *)kmalloc_a(sizeof(struct page_directory));
	memset(kernel_directory, 0, sizeof(struct page_directory));
	kernel_directory -> physaddr = (u32int)(kernel_directory -> tables_phys);

	monitor_write("[paging]placement_addr = ");
	monitor_write_hex(placement_addr);
	monitor_write("\n");

	for(i = KHEAP_START; i < KHEAP_START + KHEAP_INITSIZE; i += 0x1000){
		get_page(i, 1, kernel_directory);
	}

	i = 0;

	while(i < placement_addr + 0x1000){
		alloc_frame(get_page(i, 1, kernel_directory), 0, 0);
		i += 0x1000;
	}

	for(i = KHEAP_START; i < KHEAP_START + KHEAP_INITSIZE; i += 0x1000){
		alloc_frame(get_page(i, 1, kernel_directory), 0, 0);
	}

	reg_intrh(14, page_fault);
	switch_page_directory(kernel_directory);
	kheap = create_heap(KHEAP_START, KHEAP_START + KHEAP_INITSIZE, 0xCFFFF000, 0, 0);

	current_directory = clone_directory(kernel_directory);
/*
	monitor_write("kernel physaddr = ");
	monitor_write_hex(kernel_directory -> physaddr);
	monitor_write("\n");

	monitor_write("current physaddr = ");
	monitor_write_hex(current_directory -> physaddr);
	monitor_write("\n");
*/
	switch_page_directory(current_directory);

	for(i = UHEAP_START; i < UHEAP_START + UHEAP_INITSIZE; i += 0x1000){
		get_page(i, 1, current_directory);
	}

	for(i = UHEAP_START; i < UHEAP_START + UHEAP_INITSIZE; i += 0x1000){
		alloc_frame(get_page(i, 1, current_directory), 0, 1);
	}

	uheap = create_heap(UHEAP_START, UHEAP_START + UHEAP_INITSIZE, 0xDFFFF000, 0, 0);
	monitor_write("Paging initialized.\n");
}


void switch_page_directory(struct page_directory *new_pd){

	u32int cr0;

	static u32int reentry = 0;

	current_directory = new_pd;
	asm volatile("mov %0, %%cr3":: "r"(new_pd -> physaddr));
	asm volatile("mov %%cr0, %0": "=r"(cr0));
	cr0 |= 0x80000000;
	asm volatile("mov %0, %%cr0":: "r"(cr0));
	reentry++;
}

struct page *get_page(u32int addr, int make, struct page_directory *pd){

	u32int table_idx, tmp;

	addr /= 0x1000;
	table_idx = addr / 1024;
	if(pd -> tables[table_idx]){
		return &pd -> tables[table_idx] -> pages[addr % 1024];
	}else if(make){
		pd -> tables[table_idx] = (struct page_table *)kmalloc_ap(sizeof(struct page_table), &tmp);
		pd -> tables_phys[table_idx] = tmp | 0x7;
		return &pd -> tables[table_idx] -> pages[addr % 1024];
	}else{
		return 0;
	}
}


void page_fault(struct registers regs){

	u32int faulting_address;
	int present, rw, us, reserved, id, stub = 0;
	asm volatile("mov %%cr2, %0" : "=r" (faulting_address));
    	
	present = !(regs.err_code & 0x1); 
	rw = regs.err_code & 0x2;
	us = regs.err_code & 0x4;
	reserved = regs.err_code & 0x8;
	id = regs.err_code & 0x10;

	monitor_write("Page fault! ( ");
	if (present)	monitor_write("present ");
	if (rw)		monitor_write("read-only ");
	if (us)		monitor_write("user-mode ");
	if (reserved)	monitor_write("reserved ");
	monitor_write(") at ");
	monitor_write_hex(faulting_address);
	monitor_write(" in task ");
	monitor_write_hex(current_task -> id);
	monitor_write(" @ eip = ");
	monitor_write_hex(regs.eip);
	monitor_write("\n");
	while(1)
		stub++;
}


static struct page_table *clone_table(struct page_table *src, u32int *cphys){

	struct page_table *table;
	int i;

	table = (struct page_table *)kmalloc_ap(sizeof(struct page_table), cphys);
	memset(table, 0, sizeof(struct page_table));

	for(i = 0; i < 1024; i++){
		if(src -> pages[i].frame){
			alloc_frame(&table -> pages[i], 0, 0);

			if(src -> pages[i].present)	table -> pages[i].present = 1;
			if(src -> pages[i].rw)		table -> pages[i].rw = 1;
			if(src -> pages[i].user)	table -> pages[i].user = 1;
			if(src -> pages[i].accessed)	table -> pages[i].accessed = 1;
			if(src -> pages[i].dirty)	table -> pages[i].dirty = 1;

			copy_page_physical(src -> pages[i].frame * 0x1000, table -> pages[i].frame * 0x1000);		
		}
	}
	return table;
}


struct page_directory *clone_directory(struct page_directory *src){

	u32int phys, cphys, offset;
	int i;
	struct page_directory *dir;

	dir = (struct page_directory *)kmalloc_ap(sizeof(struct page_directory), &phys);
	memset(dir, 0, sizeof(struct page_directory));
/*
	monitor_write("[clone directory] phys = ");
	monitor_write_hex(phys);
	monitor_write("\n");
*/
	offset = (u32int)dir -> tables_phys - (u32int)(dir);
	dir -> physaddr = phys + offset;

	for(i = 0; i < 1024; i++){
		if(!src -> tables[i]){
			continue;
		}
		if(src -> tables[i] == kernel_directory -> tables[i]){
			dir -> tables[i] = src -> tables[i];
			dir -> tables_phys[i] = src -> tables_phys[i];
			if(flag_task){
				monitor_write("[linking table] table @ ");
				monitor_write_hex(dir -> tables_phys[i]);
				monitor_write("\n");
			}

		}else{
			dir -> tables[i] = clone_table(src -> tables[i], &cphys);
			dir -> tables_phys[i] = cphys | 0x07;
			if(flag_task){
				monitor_write("[cloning table] table @ ");
				monitor_write_hex(cphys | 0x07);
				monitor_write("\n");
			}
		}
	}

	return dir;
}

void scan_page_directory(struct page_directory *dir){

	s32int iter_table, iter_page, cnt_page, first_page = -1, last_page;

	for(iter_table = 0; iter_table < 1024; iter_table++){
		if((dir -> tables)[iter_table]){
			monitor_write("[scanning] table #");
			monitor_write_hex(iter_table);
			monitor_write(" @ ");
			monitor_write_hex((dir -> tables_phys)[iter_table]);
			monitor_write("\n");

			cnt_page = 0;
			for(iter_page = 0; iter_page < 1024; iter_page++){
				if((((dir -> tables)[iter_table]) -> pages[iter_page]).present){
					last_page = iter_page;
					if(first_page == -1)
						first_page = iter_page;
					cnt_page++;
				}
			}
			monitor_write("\t[scanning] cnt_page = ");
			monitor_write_hex(cnt_page);
			monitor_write(", starts @ ");
			monitor_write_hex(((u32int)(((dir -> tables)[iter_table]) -> pages[first_page]).frame) << 12);
			monitor_write(", ends @ ");
			monitor_write_hex(((u32int)(((dir -> tables)[iter_table]) -> pages[last_page]).frame) << 12);
			monitor_write("\n");
		}
	}
}	
